#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""The default IUriResolver implementation."""

namespace Grawlix.Services.Resources
import Grawlix.Properties
import Grawlix.Services
import C5
import System

transient class DefaultUriResolver(ServiceBase, IUriResolver):
"""An internally-provided service that resolves URIs into resources."""
	
	static def constructor():
	"""Defines the game URI scheme."""
		UriParser.Register(FileStyleUriParser(), _gameScheme, -1)
		UriParser.Register(FileStyleUriParser(), _internalScheme, -1)
	
	def constructor():
	"""
	Creates a new resolver with default data.
	"""
		_baseUri = Uri(_gameScheme + "://")
		_baseInternal = Uri(_internalScheme + "://")
	
	def constructor([required] searchPaths as string*):
	"""
	Creates a new resolver with specified search paths.
	Param searchPaths: A semicolon-delimetered list of search paths.
	"""
		self()
		fileBase = Uri("file://")
		for path in searchPaths:
			pathUri = Uri(IO.Path.GetFullPath(path))
			_searchPaths.Add(Uri(fileBase, pathUri))
	
	def CompleteUri([required] uri as Uri):
	"""
	Completes the given uri with the game scheme.
	Param uri: The URI to complete.
	Returns: A complete URI.
	Remarks: If the original URI is complete,
		this function will return a copy.
	"""
		return Uri(_baseUri, uri)
	
	def Resolve([required] uri as Uri, allowLocal as bool):
	"""
	Resolve a game URI into a local URI.
	Param uri: The URI of the resource.
	Param allowLocal: Allow references to the local file system.
		(Only permit in trusted scenarios or for already resolved URIs.)
	Returns: A URI that probably points to a valid local stream.
	Raises IO.FileNotFoundException: If the URI does not resolve
		into an existing resource.
	Raises IO.IOException: For other I/O related errors.
	Remarks: Can block for many reasons, such as
		if it needs to download the file.
	"""
		resolved = TryResolve(uri, allowLocal)
		if resolved is not null:
			return resolved
		raise IO.FileNotFoundException(
			"A request was made to resolve a universal resource identifier " +
			"(${uri}) but no such resource was found. " +
			"The resource requested was not necessarily a critical " +
			"component, but the data will not be accessible.")
	
	private static def GetCommonText([required] uri as Uri):
	"""
	Find the common meat of a URI.
	Param uri: The URI to process.
	Returns: The slightly formatted text.
	"""
		common = uri.AbsolutePath
		assert common.Length > 0
		assert common[0] == char('/')
		common = common.Trim(char('/'))
		if uri.Host.Length > 0:
			common = IO.Path.Combine(uri.Host, common)
		return common
	
	def TryResolve([required] uri as Uri, allowLocal as bool):
	"""
	Resolve a game URI into a local URI.
	Param uri: The URI to check.
	Param allowLocal: Allow references to the local file system.
		(Only permit in trusted scenarios or for already resolved URIs.)
	Returns: The URI if it exists, else null.
	Raises IO.IOException: In uncommon scenarios.
	Remarks: Can block for many reasons, such as
		if it needs to download the file.
	"""
		if allowLocal:
			# file://
			if uri.Scheme == Uri.UriSchemeFile:
				if IO.File.Exists(uri.LocalPath):
					return uri
				return null
			# internal://
			if uri.Scheme == _internalScheme:
				common = GetCommonText(uri)
				if common.Length == 0:
					return null
				resource = BuiltIn.ResourceManager.GetObject(
					common,
					BuiltIn.ResourceCulture)
				if resource is not null:
					return uri
				return null
		# Any other but game://
		if uri.Scheme != _gameScheme:
			return null
		common = GetCommonText(uri)
		if common.Length == 0:
			return null
		commonUri = Uri(common, UriKind.Relative)
		for resourcePath in _searchPaths:
			world = Uri(resourcePath, commonUri)
			if IO.File.Exists(world.LocalPath):
				return world
		# Internal assembly resources
		internalPath = common.Replace(char('/'), char('.'))
		resource = BuiltIn.ResourceManager.GetObject(
			internalPath,
			BuiltIn.ResourceCulture)
		if resource is not null:
			internalUri = Uri(internalPath, UriKind.Relative)
			return Uri(_baseInternal, internalUri)
		return null
	
	override def ToString():
	"""
	Get a string representation of this object.
	Returns: A string representation of this object.
	"""
		return ("DefaultUriResolver ${join(_searchPaths, ';')}")
	
	[getter(BaseUri)]
	private _baseUri as Uri
	"""The base URI used to get absolute URIs."""
	
	[getter(BaseInternal)]
	private _baseInternal as Uri
	"""Base URI used to get internal URIs."""
	
	private _searchPaths = ArrayList[of Uri]()
	"""A list of directories to search for resources in."""
	
	[getter(GameScheme)]
	private static final _gameScheme = "game"
	"""The name of the default URI scheme."""
	
	[getter(InternalScheme)]
	private static final static _internalScheme = "internal"
	"""The name of the internal resource URI scheme."""
	